﻿using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;
using Tequila.Phone.Location.ReverseGeocoding.DTO;

namespace Tequila.Phone.Location.ReverseGeocoding
{
    public class GoogleGeocodingProvider : IReverseGeocodingProvider
    {
        private const string requestUriFormat = "http://maps.googleapis.com/maps/api/geocode/json?latlng={0}&sensor=true";

        public void LookupGeoName(double latitude, double longitude, Action<GeoAddress> completedCallback)
        {
            var requestUri = GenerateRequestUri(latitude, longitude);
            var googleWebClient = new WebClient();

            googleWebClient.OpenReadCompleted += (sender, args) => ProcessResponse(completedCallback, args);
            googleWebClient.OpenReadAsync(new Uri(requestUri));
        }
      
        private string GenerateRequestUri(double latitude, double longitude)
        {
            var latlngParameterValue = string.Concat(latitude.ToString(CultureInfo.InvariantCulture).Replace(",", "."), ",", 
                longitude.ToString(CultureInfo.InvariantCulture).Replace(",", "."));

            return string.Format(requestUriFormat, latlngParameterValue);
        }

        private void ProcessResponse(Action<GeoAddress> completedCallback, OpenReadCompletedEventArgs args)
        {
            var resultGeoName = GeoAddress.Unknown;

            if (args.Error != null)
            {
                throw new LookupGeoNameException(args.Error);
            }

            try
            {
                var lookupResponse = DeserializeResponseData(args);
                if (lookupResponse == null || !lookupResponse.IsSuccess)
                {
                    return;
                }

                resultGeoName = GetAddressFromResponse(lookupResponse);
            }
            catch (Exception exp)
            {
                throw new LookupGeoNameException(exp);
            }
            finally
            {
                if (completedCallback != null)
                {
                    completedCallback(resultGeoName);
                }
            }
        }

        private GeoAddress GetAddressFromResponse(LookupGeoNameResponse lookupResponse)
        {
            var country = FetchCountryName(lookupResponse);
            var city = FetchCityName(lookupResponse);
            var address = FetchAddress(lookupResponse);

            return new GeoAddress(country, city, address);
        }

        private static string FetchCityName(LookupGeoNameResponse response)
        {
            const string cityType = "locality";
            return FetchGeoName(response, cityType);
        }

        private static string FetchCountryName(LookupGeoNameResponse response)
        {
            const string countryType = "country";
            return FetchGeoName(response, countryType);
        }

        private static string FetchAddress(LookupGeoNameResponse response)
        {
            var geoInfo = response.Results.FirstOrDefault();
            if (geoInfo != null)
            {
                return geoInfo.FormattedAddress;
            }

            return string.Empty;
        }

        private static string FetchGeoName(LookupGeoNameResponse response, string type)
        {
            //when geo info is not found by type will ise first geo ingo and try fetch geo name from Adresses
            var geoInfo = response.Results.FirstOrDefault(r => Array.IndexOf(r.Types, type) != -1) ??
                          response.Results.FirstOrDefault();

            if (geoInfo != null)
            {
                var geoName = geoInfo.Addresses.FirstOrDefault(a => Array.IndexOf(a.Types, type, 0) != -1);
                return geoName != null ? geoName.LongName : string.Empty;
            }

            return string.Empty;
        }

        private static LookupGeoNameResponse DeserializeResponseData(OpenReadCompletedEventArgs args)
        {
            var serializer = new DataContractJsonSerializer(typeof (LookupGeoNameResponse));
            var lookupResponse = serializer.ReadObject(args.Result) as LookupGeoNameResponse;
            return lookupResponse;
        }
    }
}
